/**
 *
 * 航天二院二部报告
 *
 * Created by nongzhizhong on 2024/03/05
 */

const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const BaseReporter = require('./../base-report');
const fileModels = require('./../../../files');
const testModels = require('./../../../tests');
const functionModels = require('./../../../functions');
const classCreator = require('./../base-report-test-case-transformer');
const utility = require('../../../utility');
const logger = require('../../../logger');

const split = false;

class Casic2Yuan2BuReport extends BaseReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, __dirname, format);
    this.reportTime = moment().format('YYYY年MM月');
    this.complete = complete || false;
    this.testcaseNumber = 0;
  }

  async generate() {
    const result = {
      cachePath: this.outputDir,
    };
    const { versionPath, projectDir } = await this.spliteByProjectVersion();
    this.versionDir = versionPath;
    this.projectDir = projectDir;
    const files = await this.spliteByFile();
    const fileName = `${this.project.projectName}-${this.version.versionName}.${this.isIntegrationTest() ? '集成测试报告' : '单元测试报告'}`;
    // 拆分文件
    if (!split) {
      const reportPath = `${path.join(this.versionDir, fileName)}`;
      await this.generateFileReport(
        {
          projectName: this.project.projectName,
          versionName: this.version.versionName,
          files: files.filter((file) => file.functions.length > 0),
          reportTime: this.reportTime,
        },
        reportPath
      );
    }
    result.reportPath = path.join(this.outputDir, `${fileName}.zip`);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  async spliteByFile() {
    await fs.ensureDir(this.versionDir);
    const sourceFileExts = [];
    const languages = { ...this.version.languages, headers: this.version.headers };
    Object.keys(languages).forEach((key) => {
      if (languages[key].suffixes) {
        sourceFileExts.push(...languages[key].suffixes);
      }
    });
    const resFiles = await fileModels.getSourceFileByVersionId(this.versionId, sourceFileExts, ['_id', 'fileName', 'path']);
    return this.handleFiles([...resFiles.values()], this.versionDir);
  }

  async handleFiles(files, versionDir) {
    return utility.arrayChunkOperation(files, 1, (file) => this.handleFile(file, versionDir));
  }

  async handleFile(file) {
    const result = {
      fileName: file.fileName,
      filePath: file.path,
      functions: [],
    };
    const fileFunctionsInfo = await super.handleFile(file, this.versionDir);
    result.functions = (await this.getFunctionDetailsOfFile(fileFunctionsInfo.functions, fileFunctionsInfo.typeSystem))
      .sort((a, b) => (a.index > b.index ? 1 : -1));
    // 重置测试用例序号
    result.functions.forEach((func) => {
      func.tests.forEach((test) => {
        test.number = this.testcaseNumber + 1;
        this.testcaseNumber += 1;
      });
    });
    if (split && result.functions.length > 0) {
      const reportPath = `${path.join(this.versionDir, file.path)}`;
      await fs.ensureDir(path.dirname(reportPath));
      return this.generateFileReport(
        {
          projectName: this.project.projectName,
          versionName: this.version.versionName,
          files: [result],
          reportTime: this.reportTime,
        },
        reportPath
      );
    }
    return result;
  }

  generateFileReport(data, output) {
    return this.generateReport('file-template', data, output);
  }

  /**
  *  格式化函数全名，包含形参
  * @param {String} functionName 函数名
  * @param {Object} params 函数variable形参
  */
  getFunctonFullName(functionName, params) {
    const regExpr = /(.*)\(.*\)/;
    if (functionName.match(regExpr)) {
      [, functionName] = functionName.match(regExpr);
    }
    if (Object.keys(params).length > 0) {
      const paramsArr = [];
      Object.keys(params).forEach((name) => {
        if (params[name]['@index'] && params[name]['@type']) {
          paramsArr[parseInt(params[name]['@index'], 10)] = `${params[name]['@type']} ${name}`;
        }
      });
      if (paramsArr.length > 0) {
        functionName = `${functionName}(${paramsArr.toString()})`;
      }
    }
    return functionName;
  }

  /**
   *  获取函数详情
   * @param {Array[Object]} functions 函数
   * @param {Object} typeSystem 文件类型系统
   */
  async getFunctionDetailsOfFile(functions, typeSystem) {
    const functionsDetails = [];
    await utility.arrayChunkOperation(functions, 10, async (func) => {
      const { _id: functionId } = func;
      if (func.tests.length === 0) {
        return;
      }
      const functionCFG = await functionModels.getFunctionCFG(functionId);
      const funcVariables = functionModels.getFunctionVariableJson(func.variables);
      const testcasesDetails = await this.getTestDetailsOfFunction(
        typeSystem,
        func,
        funcVariables,
        func.tests,
        {
          branches: functionCFG.branches,
          decisions: functionCFG.decisions,
          nodes: functionCFG.nodes,
        }
      );
      functionsDetails.push({
        functionFullName: this.getFunctonFullName(func.functionName, funcVariables.variables ? (funcVariables.variables.params || {}) : {}),
        functionName: func.functionName,
        index: func.index,
        tests: testcasesDetails,
      });
    });
    return functionsDetails;
  }

  /**
   * 获取测试用例分支预期结果表达式信息
   * @param {String} testcaseId 测试用例id
   * @param {Object} functionCFG 函数cfg
   */
  getTestExpectedResultDetails(testcaseId, functionCFG) {
    const { branches, decisions, nodes } = functionCFG;
    if (branches
      && Object.keys(branches).length > 0
      && nodes
      && Object.keys(nodes).length > 0
      && decisions
      && Object.keys(decisions).length > 0) {
      const branchExprs = [];
      Object.keys(branches).forEach((branchKey) => {
        const branch = branches[branchKey];
        if (branch.decisionId === undefined || !decisions[branch.decisionId]) {
          return;
        }
        const { expr } = decisions[branch.decisionId];
        Object.keys(branch.branches).forEach((nodeId) => {
          const branchNode = branch.branches[nodeId];
          if (branchNode
            && branchNode.kind === 'BOOLEAN'
            && branchNode.testIds
            && branchNode.testIds[testcaseId]
            && branchNode.testIds[testcaseId] > 0
            && nodes[nodeId]
            && nodes[nodeId].begin) {
            branchExprs.push({
              expr: `程序执行${expr}分支为${branchNode.value === '1'},进入${nodes[nodeId].begin.line || ''}行;`,
            });
          }
        });
      });
      return branchExprs;
    }
    return [];
  }

  /**
   * 获取测试用例详情
   * @param {Object} typeSystem 文件类型系统
   * @param {Object} func  函数
   * @param {Object} funcVariables 函数variable
   * @param {Array[Object]} testcases 测试用例
   * @param {Object} funcCfg 函数cfg
   */
  async getTestDetailsOfFunction(typeSystem, func, funcVariables, testcases, functionCFG) {
    const testcasesDetails = [];
    const { coverages, mcdcKind } = this.version;
    const testcaseIds = testcases.map((item) => item._id);
    // 计算累计覆盖率
    const accumulationCoverages = await functionModels.getTestcasesCoverage(func._id, testcaseIds, mcdcKind);
    testcases.forEach((testcase, index) => {
      const newTestcase = testModels.getJson(testcase, this.i18n);
      const ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
      const transformer = new ReportTestCaseTransformer(
        newTestcase,
        funcVariables,
        typeSystem,
        this.i18n
      );
      try {
        transformer.transform(this.complete);
      } catch (error) {
        logger.error(`testCase transform error: fileId: ${func.fileId} function name: ${func.functionName} test case number: ${testcase.number}:`, error);
      }
      let statementCoverage;
      let branchCoverage;
      let mcdcCoverage;
      let accStatementCoverage;
      let accBranchCoverage;
      let accMcdcCoverage;
      const accumulationCoverage = accumulationCoverages[index] || {};
      function handleConveragePercent(coverage) {
        return `${Math.round(coverage * 10000) / 100}%`;
      }
      if ((coverages & testModels.coverageKind.statement) > 0) {
        statementCoverage = '-';
        accStatementCoverage = '-';
        if (func.statementTotal && !this.isValidResultKind(testcase.resultKind)) {
          statementCoverage = '0%';
        } else if (func.statementTotal) {
          statementCoverage = testcase.statementCovered / func.statementTotal;
          statementCoverage = handleConveragePercent(statementCoverage);
        }
        // 计算语句累计覆盖率
        if (accumulationCoverage.statementTotal) {
          accStatementCoverage = accumulationCoverage.statementCovered / accumulationCoverage.statementTotal;
          accStatementCoverage = handleConveragePercent(accStatementCoverage);
        }
      }
      if ((coverages & testModels.coverageKind.branch) > 0) {
        branchCoverage = '-';
        accBranchCoverage = '-';
        if (func.branchTotal && !this.isValidResultKind(testcase.resultKind)) {
          branchCoverage = '0%';
        } else if (func.branchTotal) {
          branchCoverage = testcase.branchCovered / func.branchTotal;
          branchCoverage = handleConveragePercent(branchCoverage);
        }
        // 计算分支累计覆盖率
        if (accumulationCoverage.branchTotal) {
          accBranchCoverage = accumulationCoverage.branchCovered / accumulationCoverage.branchTotal;
          accBranchCoverage = handleConveragePercent(accBranchCoverage);
        }
      }
      if ((coverages & testModels.coverageKind.mcdc) > 0) {
        mcdcCoverage = '-';
        accMcdcCoverage = '-';
        if (func.mcdcTotal && !this.isValidResultKind(testcase.resultKind)) {
          mcdcCoverage = '0%';
        } else if (func.mcdcTotal) {
          mcdcCoverage = testcase.mcdcCovered / func.mcdcTotal;
          mcdcCoverage = handleConveragePercent(mcdcCoverage);
        }
        // 计算MCDC累计覆盖率
        if (accumulationCoverage.mcdcTotal) {
          accMcdcCoverage = accumulationCoverage.mcdcCovered / accumulationCoverage.mcdcTotal;
          accMcdcCoverage = handleConveragePercent(accMcdcCoverage);
        }
      }
      if (utility.isArrayType(transformer.input)) {
        transformer.input.forEach((input) => {
          // eslint-disable-next-line no-control-regex
          input.variable = input.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
          // 处理变量值显示格式
          if (input.value) {
            input.value = ` = ${input.value}`;
          }
        });
      }
      if (utility.isArrayType(transformer.output)) {
        transformer.output.forEach((output) => {
          // eslint-disable-next-line no-control-regex
          output.variable = output.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
          // 处理变量值显示格式
          if (output.result) {
            output.result = ` = ${output.result}`;
          }
        });
      }
      const expectedResultDetails = this.getTestExpectedResultDetails(testcase._id.toString(), functionCFG);
      const newInputDetails = [];
      const newOutputDetails = [];
      transformer.input.forEach((vobj) => {
        if (vobj.category && vobj.variable) {
          newInputDetails.push({
            category: `${vobj.category}:`,
            value: '',
            variable: '',
          });
          newInputDetails.push({
            category: '',
            value: vobj.value,
            variable: vobj.variable,
          });
        } else {
          newInputDetails.push(vobj);
        }
      });
      transformer.output.forEach((vobj) => {
        if (vobj.category && vobj.variable) {
          newOutputDetails.push({
            category: `${vobj.category}:`,
            value: '',
            result: '',
            variable: '',
          });
          newOutputDetails.push({
            category: '',
            value: vobj.value,
            result: vobj.result,
            variable: vobj.variable,
          });
        } else {
          newOutputDetails.push(vobj);
        }
      });
      testcasesDetails.push({
        index: index + 1,
        testId: testcase._id,
        resultKind: this.i18n.__(`tests.testResult.${BaseReporter.getTestResultKindKey(
          testcase.resultKind === testModels.resultKind.noResult ? testModels.resultKind.succeed : testcase.resultKind
        )}`),
        testcaseDesc: `test_${func.functionName}_${'0'.repeat(3 - `${index + 1}`.length)}${index + 1}`,
        details: {
          input: newInputDetails.length === 0 ? [{ category: '', variable: '', value: '无' }] : newInputDetails,
          output: newOutputDetails.length === 0 && expectedResultDetails.length === 0 ? [{ category: '', variable: '', result: '正常执行' }] : newOutputDetails,
        },
        expectedResultDetails,
        statementCoverage,
        branchCoverage,
        mcdcCoverage,
        accStatementCoverage,
        accBranchCoverage,
        accMcdcCoverage,
      });
    });
    return testcasesDetails;
  }

  /**
   *  过滤测试用例结果
   * @param {number} resultKind 测试用例结构kind
   * @returns
   */
  isValidResultKind(resultKind) {
    return [
      testModels.resultKind.succeed,
      testModels.resultKind.nonStrictSucceed,
      testModels.resultKind.noResult,
    ].indexOf(resultKind) >= 0;
  }
}

module.exports = Casic2Yuan2BuReport;
